// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




#if !S3SOAP
using System;
using System.IO;
using System.IO.Compression;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Diagnostics;
using CIP4.Common;
using CIP4.Item;
using CIP4.Simple;
using com.amazon.s3;

namespace CIP4.DataProvider.S3
{
    class S3BucketType
    {
        #region Static Features

        private static string AWSAccessKey { get; set; }

        private static string AWSSecretKey { get; set; }

        [ThreadStatic]
        public static Dictionary<Guid, S3BucketType> Buckets;

        public static void Initialize(string accessKey, string secretKey)
        {
            AWSAccessKey = accessKey;
            AWSSecretKey = secretKey;
        }

        public static S3BucketType GetBucket(CollectionType collection, IDataProvider cachingProvider, LoggerWrapperDelegate loggerWrapper)
        {
            if (Buckets == null)
                Buckets = new Dictionary<Guid, S3BucketType>();
            if (!Buckets.ContainsKey(collection.ExternalIDGuid()))
                Buckets.Add(collection.ExternalIDGuid(), new S3BucketType(collection, cachingProvider, loggerWrapper));
            return Buckets[collection.ExternalIDGuid()];
        }

        #endregion

        #region Privates

        const int BUCKETPAGESIZE = 100;

        AWSAuthConnection connection;

        IDataProvider Cache { get; set; }

        #endregion
        #region Public Features & Constructors

        public LoggerWrapperDelegate LoggerWrapper { get; set; }

        public CollectionType BucketCollection { get; set; }

        private S3BucketType(CollectionType collection, IDataProvider cachingProvider, LoggerWrapperDelegate loggerWrapper)
        {
            LoggerWrapper = loggerWrapper;
            connection = new AWSAuthConnection(AWSAccessKey, AWSSecretKey);
            Cache = cachingProvider;
            BucketCollection = collection;
            CreateBucket(BucketCollection.ExternalIDGuid().ToString());
        }

        public List<object> GetItems(IBooleanValue selectionExpression, CursorType cursor)
        {
            List<object> result = new List<object>();
            try
            {
                string lookForExternalId = selectionExpression == null? null : selectionExpression.ComparesTo("ExternalID").FirstOrDefault();

                if (!string.IsNullOrEmpty(lookForExternalId))
                    GetItem(lookForExternalId, result, selectionExpression, cursor.GetSerializer(SerializerKindEnum.CIP4Xml));
                else
                {
                    using (ListBucketResponse listBucketResponse = connection.listBucket(BucketCollection.ExternalIDGuid().ToString(), null, cursor.Marker, cursor.PageSize, null))
                    {
                        //marker = listBucketResponse.Marker;
                        foreach (com.amazon.s3.ListEntry entry in listBucketResponse.Entries)
                        {
                            cursor.Marker = entry.Key;
                            GetItem(entry.Key, result, selectionExpression, cursor.GetSerializer(SerializerKindEnum.CIP4Xml));
                        }
                    }
                }
            }
            catch (System.Net.WebException ex)
            {
                if (!(ex.InnerException != null && ex.InnerException is System.Net.WebException &&
                    ex.InnerException.Message == "The remote server returned an error: (404) Not Found."))
                {
                    LoggerWrapper(MajorEnum.Error, MinorEnum.TheRemoteServeReturnedAnError404NotFound, 
                        "The S3 server returned an error 404 (not found)", new object[] { ex.Message }, cursor.Logger);
                    throw;
                }
            }
            return result;
        }

        public int SaveItems(IEnumerable<object> items, CollectionType collection, ISerializerType serializer, SetHeaderPropertiesDelegate SetHeader)
        {
            int itemCount = 0;
            foreach (object itemObject in items)
            {
                if (!SetHeader(itemObject, collection, serializer))
                    continue;
                SortedList headers = new SortedList();
                headers.Add("x-amz-acl", "private");
                headers.Add("Content-Type", "text/plain");
                SortedList metadata = new SortedList();
                metadata.Add("collection", BucketCollection.ExternalIDGuid().ToString());
                S3Object obj = new S3Object(Serializer.SerializerBaseType.Compress(serializer.GetDataForObject(itemObject).ToString()), metadata);
                using (Response response = connection.put(BucketCollection.ExternalIDGuid().ToString(), serializer.GetExternalID(itemObject).ToString(), obj, headers))
                {
                    if (response.Status != System.Net.HttpStatusCode.OK)
                    {
                        LoggerWrapper(MajorEnum.Error, MinorEnum.ErrorSavingItems, "Error on S3 Put: {0}", new object[] { response.ToString() }, serializer.Logger);
                    }
                    else
                    {
                        itemCount++;
                    }
                }
            }
            return itemCount;
        }

        public bool PurgeAllItems(CollectionType collection, LoggerDelegate logger)
        {
            int retryCount = 0;
            while (retryCount < 3)
            {
                try
                {
                    bool doneAll = false;
                    while (!doneAll)
                    {
                        string marker = string.Empty;
                        using (ListBucketResponse listBucketResponse = connection.listBucket(BucketCollection.ExternalIDGuid().ToString(), null, marker, BUCKETPAGESIZE, null))
                        {
                            doneAll = listBucketResponse.Entries.Count == 0;
                            foreach (com.amazon.s3.ListEntry entry in listBucketResponse.Entries)
                            {
                                marker = entry.Key;
                                using (Response response = connection.delete(BucketCollection.ExternalIDGuid().ToString(), entry.Key, null))
                                {
                                    if (response.Status != System.Net.HttpStatusCode.NoContent)
                                        LoggerWrapper(MajorEnum.Error, MinorEnum.ErrorPurgingItems,
                                                "Error on delete: {0}", new object[] { response.getResponseMessage() }, logger);
                                    else
                                        LoggerWrapper(MajorEnum.Trace, MinorEnum.ItemDeleted,
                                                "Item {0} deleted ", new object[] { entry.Key }, logger);
                                }
                            }
                        }
                    }
                    return true;
                }
                catch (System.Net.WebException ex)
                {
                    if (ex.InnerException is System.Net.WebException &&
                        ex.InnerException.Message == "The remote server returned an error: (500) Internal Server Error.")
                    {
                        retryCount += 1;
                        LoggerWrapper(MajorEnum.Warning, MinorEnum.ErrorPurgingItems, "Error purging an item - retrying after: {0}", new object[] {ex.Message}, logger);
                    }
                    else
                    {
                        LoggerWrapper(MajorEnum.Error, MinorEnum.ErrorPurgingItems, "Error purging an item: {0}", new object[] { ex.Message }, logger);
                        break;
                    }
                }
            }
            LoggerWrapper(MajorEnum.Error, MinorEnum.RetryCountExceeded, 
                "Retry count exceeded in PurgeAllItems for {0}", new object[] { BucketCollection.Name}, logger);
            return false;
        }

        public int PurgeItems(IEnumerable<ExternalIDType> externalIDs, ISerializerType serializer)
        {
            int itemCount = 0;
            foreach (ExternalIDType externalID in externalIDs)
            {
                using (Response response = connection.delete(BucketCollection.ExternalIDGuid().ToString(), externalID.ToString(), null))
                {
                    if (response.Status != System.Net.HttpStatusCode.NoContent)
                    {
                        LoggerWrapper(MajorEnum.Error, MinorEnum.ErrorPurgingItems,
                                "Error on delete: {0}", new object[] { response.getResponseMessage() }, serializer.Logger);
                    }
                    else
                    {
                        itemCount++;
                        LoggerWrapper(MajorEnum.Trace, MinorEnum.ItemDeleted,
                            "Item {0} was deleted", new object[] { externalID }, serializer.Logger);
                    }
                }
            }
            return itemCount;
        }

        public bool DeleteCollection(CollectionType collection, LoggerDelegate logger)
        {
            if (PurgeAllItems(collection, logger))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        #endregion
        #region Private Functions

        private bool GetItem(string externalID, List<object> result, IBooleanValue selectionExpression, ISerializerType serializer)
        {
            GetResponse response = connection.get(BucketCollection.ExternalIDGuid().ToString(), externalID, null);
            string text = Serializer.SerializerBaseType.DeCompress(response.Object.Bytes);
            object item = serializer.GetObjectForData(XElement.Parse(text));

            if (DataProvider.CompareProperties(selectionExpression, item, serializer))
            {
                result.Add(item);
                return true;
            }
            else
            {
                return false;
            }
        }

        private void CreateBucket(string bucketName)
        {
            if (connection.checkBucketExists(bucketName))
                return;
            using (Response response = connection.createBucket(bucketName, Location.DEFAULT, null))
            {
                System.Console.WriteLine(response.getResponseMessage());
            }
        }
        
        #endregion
    }
}
#endif